package com.my.article.service.serviceImpl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.my.apis.user.IApUserClient;
import com.my.article.mapper.ApArticleConfigMapper;
import com.my.article.mapper.ApArticleContentMapper;
import com.my.article.mapper.ApArticleMapper;
import com.my.article.service.ApArticleService;
import com.my.article.service.ArticleFreemarkerService;
import com.my.common.constans.ArticleConstas;
import com.my.common.constans.HotArticleConstants;
import com.my.common.redis.CacheService;
import com.my.file.service.FileStorageService;
import com.my.model.article.dtos.ArticleBehaviorRespDto;
import com.my.model.article.dtos.ArticleDto;
import com.my.model.article.dtos.ArticleHomeDto;
import com.my.model.article.pojos.ApArticle;
import com.my.model.article.pojos.ApArticleConfig;
import com.my.model.article.pojos.ApArticleContent;
import com.my.model.article.vos.HotArticleVo;
import com.my.model.common.dtos.ResponseResult;
import com.my.model.common.enums.AppHttpCodeEnum;
import com.my.model.mess.ArticleVisitStreamMess;
import com.my.model.mess.UpdateArticleMess;
import com.my.model.user.pojos.ApUser;
import com.my.utils.thread.AppThreadLocalUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Transactional
@Slf4j
public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;

    //单页最大加载文章数
    private final static short MAX_ARTICLE_SIZE = 50;

    /**
     * 根据类型加载文章
     * @param loadtype 1.加载更多 2.加载最新
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
        //1.参数校验
        //1.1校验单页加载文章数
        Integer size = dto.getSize();
        if(size == null || size == 0) {
            log.warn("未设置单页加载文章数");
            size = 10;
        }
        size = Math.min(size, MAX_ARTICLE_SIZE);
        dto.setSize(size);

        //1.2类型参数校验
        if(!loadtype.equals(ArticleConstas.LOADTYPE_LOAD_MORE) && !loadtype.equals(ArticleConstas.LOADTYPE_LOAD_NEW)) {
            loadtype = ArticleConstas.LOADTYPE_LOAD_MORE;
        }
        //1.3文章频道校验
        if(dto.getTag().isEmpty()) {
            dto.setTag(ArticleConstas.DEFAULT_TAG);
        }
        //前端Bug校正(tag=0时候表示“其他”频道，id为7)
        if(dto.getTag().equals("0")) {
            dto.setTag("7");
        }

        //1.4时间校验
        if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());

        //2.查询文章
        List<ApArticle> articleList = apArticleMapper.loadArticleList(dto, loadtype);
        //3.返回结果
        return ResponseResult.okResult(articleList);
    }

    @Autowired
    private CacheService cacheService;
    /**
     * 加载文章列表
     * @param dto
     * @param type      1 加载更多   2 加载最新
     * @param firstPage true  是首页  flase 非首页
     * @return
     */
    @Override
    public ResponseResult load2(ArticleHomeDto dto, Short type, boolean firstPage) {
        if(firstPage){
            String jsonStr = cacheService.get(ArticleConstas.HOT_ARTICLE_FIRST_PAGE + dto.getTag());
            if(StringUtils.isNotBlank(jsonStr)){
                List<HotArticleVo> hotArticleVoList = JSON.parseArray(jsonStr, HotArticleVo.class);
                ResponseResult responseResult = ResponseResult.okResult(hotArticleVoList);
                return responseResult;
            }
        }
        return load(type,dto);
    }

    @Autowired
    private ApArticleConfigMapper apArticleConfigMapper;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ArticleFreemarkerService articleFreemarkerService;
    /**
     * 保存app端文章
     * @param dto
     * @return
     */
    @Override
    public ResponseResult saveArticle(ArticleDto dto) {
        log.info("开始保存或修改app端文章...");
        //1.参数校验
        if(dto == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        //2.拷贝属性
        ApArticle apArticle = new ApArticle();
        BeanUtils.copyProperties(dto,apArticle);

        //3.文章id为空，直接保存
        if(dto.getId() == null) {
            log.info("保存文章...");
            //3.1保存文章
            save(apArticle);

            //3.2保存文章配置
            ApArticleConfig apArticleConfig = new ApArticleConfig(apArticle.getId());
            apArticleConfigMapper.insert(apArticleConfig);

            //3.3保存文章内容
            ApArticleContent apArticleContent = new ApArticleContent();
            apArticleContent.setArticleId(apArticle.getId());
            apArticleContent.setContent(dto.getContent());
            apArticleContentMapper.insert(apArticleContent);
        } else {
            log.info("修改文章...");
            //4.文章存在id，修改文章内容
            //4.1更新文章信息
            updateById(apArticle);

            //4.2更新文章内容
            String content = dto.getContent();
            ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.
                    <ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, dto.getId()));
            apArticleContent.setContent(content);
            apArticleContentMapper.updateById(apArticleContent);

            //4.3修改文章状态
            ApArticleConfig apArticleConfig = apArticleConfigMapper.selectOne(Wrappers.<ApArticleConfig>lambdaQuery().
                    eq(ApArticleConfig::getArticleId, dto.getId()));
            if(apArticleConfig != null && apArticleConfig.getIsDown()) {
                apArticleConfig.setIsDown(false);
                apArticleConfigMapper.updateById(apArticleConfig);
            }
        }

        //5.异步调用生成静态页面上传值MinIO
        articleFreemarkerService.buildArticleToMinIO(apArticle,dto.getContent());

        return ResponseResult.okResult(apArticle.getId());
    }

    @Autowired
    private FileStorageService fileStorageService;
    /**
     * 删除app文章
     * @param id  文章id
     * @return
     */
    @Override
    public ResponseResult deleteArticle(Long id) {
        //1.参数校验
        if(id == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        log.info("开始删除app文章...");
        //2.删除minio中的文件
        ApArticle article = getById(id);
        if(article != null && article.getStaticUrl() != null) {
            log.info("删除minio中上传的html文件:{}",article.getStaticUrl());
            fileStorageService.delete(article.getStaticUrl());
        }
        //3.删除文章
        removeById(id);

        //4.删除文章配置信息
        apArticleConfigMapper.delete(Wrappers.<ApArticleConfig>lambdaQuery().eq(ApArticleConfig::getArticleId,id));

        //5.删除文章内容信息
        apArticleContentMapper.delete(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId,id));

        log.info("成功删除id为{}的文章",id);
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 文章上下架
     * @param message
     * @return
     */
    @Override
    public ResponseResult downOrUp(String message) {
        Map map = JSON.parseObject(message, Map.class);
        //获取文章id
        Long articleId = (Long) map.get("articleId");
        //获取文章待修改状态
        Integer enable = (Integer) map.get("enable");
        //查询文章配置
        ApArticleConfig apArticleConfig = apArticleConfigMapper.selectOne
                (Wrappers.<ApArticleConfig>lambdaQuery().eq(ApArticleConfig::getArticleId, articleId));
        if(apArticleConfig != null) {
            //上架
            if(enable == 1) {
                log.info("文章重新上架");
                apArticleConfig.setIsDown(false);
                apArticleConfigMapper.updateById(apArticleConfig);
            }
            //下架
            if(enable == 0) {
                log.info("文章下架");
                apArticleConfig.setIsDown(true);
                apArticleConfigMapper.updateById(apArticleConfig);
            }
        }
        else {
            throw new RuntimeException("文章信息不存在");
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    @Resource
    private IApUserClient apUserClient;
    /**
     * 展示文章行为
     * @param map
     * @return
     */
    @Override
    public ResponseResult loadArticleBehavior(Map map) {
        if(map == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        ArticleBehaviorRespDto respDto = new ArticleBehaviorRespDto();
        //判断是否关注该作者
        if(isFollow(map)) {
            respDto.setIsfollow(true);
        }

        return ResponseResult.okResult(respDto);
    }

    /**
     * 判断是否关注
     * @param map
     * @return
     */
    private boolean isFollow(Map map) {
        boolean flag = false;
        //获取当前登录用户信息
        ApUser user = AppThreadLocalUtils.getUser();
        Integer userId = user.getId();

        //获取当前浏览文章作者信息
        Integer authorId = (Integer) map.get("authorId");
        ResponseResult result = apUserClient.getUserFollow(userId, authorId);

        if(result.getCode().equals(200) && (boolean)result.getData()) {
            flag = true;
        }
        return flag;
    }

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;
    /**
     * 读文章行为记录(阅读量+1)
     * @param map
     * @return
     */
    public ResponseResult readBehavior(Map map) {
        if(map == null || map.get("articleId") == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        Long articleId = Long.parseLong((String) map.get("articleId"));
        ApArticle apArticle = getById(articleId);

        if(apArticle != null) {
            //获取文章阅读数
            Integer views = apArticle.getViews();
            if(views == null) {
                views = 0;
            }

            //调用Kafka发送消息
            UpdateArticleMess mess = new UpdateArticleMess();
            mess.setArticleId(articleId);
            mess.setType(UpdateArticleMess.UpdateArticleType.VIEWS);
            mess.setAdd(1);
            kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC,JSON.toJSONString(mess));

            //更新文章阅读数
            LambdaUpdateWrapper<ApArticle> luw = new LambdaUpdateWrapper<>();
            luw.eq(ApArticle::getId,articleId);
            luw.set(ApArticle::getViews,views + 1);
            update(luw);
        }

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 用户点赞
     * @param map
     * @return
     */
    @Override
    public ResponseResult likesBehavior(Map map) {
        if(map == null || map.get("articleId") == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        Long articleId = Long.parseLong((String) map.get("articleId"));
        Integer operation = (Integer) map.get("operation");
        ApArticle apArticle = getById(articleId);

        UpdateArticleMess mess = new UpdateArticleMess();
        mess.setArticleId(articleId);
        mess.setType(UpdateArticleMess.UpdateArticleType.LIKES);

        if(apArticle != null) {
            //获取文章点赞数
            Integer likes = apArticle.getLikes();
            if(likes == null) {
                likes = 0;
            }

            //更新文章点赞数
            LambdaUpdateWrapper<ApArticle> luw = new LambdaUpdateWrapper<>();
            luw.eq(ApArticle::getId,articleId);
            if(operation == 0) {
                //点赞
                log.info("用户点赞文章...");
                luw.set(ApArticle::getLikes,likes + 1);
                //分值增加
                mess.setAdd(1);
            } else {
                //取消点赞
                log.info("用户取消点赞文章...");
                luw.set(ApArticle::getLikes,likes - 1);
                //分值减少
                mess.setAdd(-1);
            }

            //调用Kafka发送消息
            kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC,JSON.toJSONString(mess));

            update(luw);
        }

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 用户收藏
     * @param map
     * @return
     */
    @Override
    public ResponseResult collBehavior(Map map) {
        if(map == null || map.get("entryId") == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        Long articleId = Long.parseLong((String) map.get("entryId"));
        Integer operation = (Integer) map.get("operation");
        ApArticle apArticle = getById(articleId);

        //消息载体
        UpdateArticleMess mess = new UpdateArticleMess();
        mess.setArticleId(articleId);
        mess.setType(UpdateArticleMess.UpdateArticleType.COLLECTION);

        if(apArticle != null) {
            //获取文章收藏数
            Integer collection = apArticle.getCollection();
            if(collection == null) {
                collection = 0;
            }

            //更新文章收藏数
            LambdaUpdateWrapper<ApArticle> luw = new LambdaUpdateWrapper<>();
            luw.eq(ApArticle::getId,articleId);
            if(operation == 0) {
                //收藏
                log.info("用户收藏文章...");
                luw.set(ApArticle::getCollection,collection + 1);
                mess.setAdd(1);
            } else {
                //取消收藏
                log.info("用户取消收藏文章...");
                luw.set(ApArticle::getCollection,collection - 1);
                mess.setAdd(-1);
            }

            //调用Kafka发送消息
            kafkaTemplate.send(HotArticleConstants.HOT_ARTICLE_SCORE_TOPIC,JSON.toJSONString(mess));

            update(luw);
        }

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    /**
     * 更新文章分值，同时更新redis中热点文章数据
     * @param mess
     */
    @Override
    public void updateScore(ArticleVisitStreamMess mess) {
        //1.获取文章数据
        ApArticle apArticle = getById(mess.getArticleId());
        //2.计算文章分值
        Integer score = computeScore(apArticle);
        score = score * 3;

        //3.替换当前文章对应频道热点数据
        replaceDataToRedis(apArticle,score,ArticleConstas.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId());

        //4.替换推荐频道文章热点数据
        replaceDataToRedis(apArticle,score,ArticleConstas.HOT_ARTICLE_FIRST_PAGE + ArticleConstas.DEFAULT_TAG);
    }

    /**
     * 根据权重计算文章分值
     * @param apArticle
     * @return
     */
    private Integer computeScore(ApArticle apArticle) {
        Integer score = 0;
        if(apArticle.getLikes() != null){
            score += apArticle.getLikes() * ArticleConstas.HOT_ARTICLE_LIKE_WEIGHT;
        }
        if(apArticle.getViews() != null){
            score += apArticle.getViews();
        }
        if(apArticle.getComment() != null){
            score += apArticle.getComment() * ArticleConstas.HOT_ARTICLE_COMMENT_WEIGHT;
        }
        if(apArticle.getCollection() != null){
            score += apArticle.getCollection() * ArticleConstas.HOT_ARTICLE_COLLECTION_WEIGHT;
        }

        return score;
    }

    /**
     * 替换数据并存入到redis
     * @param apArticle 文章信息
     * @param score 文章新的得分
     * @param key redis数据的key值
     */
    private void replaceDataToRedis(ApArticle apArticle,Integer score, String key) {
        String articleListStr = cacheService.get(key);
        if(StringUtils.isNotBlank(articleListStr)) {
            List<HotArticleVo> hotArticleVos = JSON.parseArray(articleListStr, HotArticleVo.class);

            boolean flag = true;

            //如果缓存中存在该文章，直接更新文章分值
            for (HotArticleVo hotArticleVo : hotArticleVos) {
                if(hotArticleVo.getId().equals(apArticle.getId())) {
                    if(key.equals(ArticleConstas.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId())) {
                        log.info("频道{}缓存中存在该文章，文章{}分值更新{}-->{}",apArticle.getChannelName(),apArticle.getId(),hotArticleVo.getScore(),score);
                    } else {
                        log.info("推荐频道缓存中存在该文章，文章{}分值更新{}-->{}",apArticle.getId(),hotArticleVo.getScore(),score);
                    }
                    hotArticleVo.setScore(score);
                    flag = false;
                    break;
                }
            }

            //如果缓存中不存在该文章
            if(flag) {
                //缓存中热点文章数少于30，直接增加
                if(hotArticleVos.size() < 30) {
                    log.info("该文章{}不在缓存，但是文章数少于30，直接添加",apArticle.getId());
                    HotArticleVo hotArticleVo = new HotArticleVo();
                    BeanUtils.copyProperties(apArticle,hotArticleVo);
                    hotArticleVo.setScore(score);
                    hotArticleVos.add(hotArticleVo);
                } else {
                    //缓存中热点文章数大于或等于30
                    //1.排序
                    hotArticleVos = hotArticleVos.stream().sorted(Comparator.
                            comparing(HotArticleVo::getScore).reversed()).collect(Collectors.toList());
                    //2.获取最小得分值
                    HotArticleVo minScoreHotArticleVo = hotArticleVos.get(hotArticleVos.size() - 1);
                    if(minScoreHotArticleVo.getScore() <= score) {
                        //3.移除分值最小文章
                        log.info("替换分值最小的文章...");
                        hotArticleVos.remove(minScoreHotArticleVo);
                        HotArticleVo hotArticleVo = new HotArticleVo();
                        BeanUtils.copyProperties(apArticle,hotArticleVo);
                        hotArticleVo.setScore(score);
                        hotArticleVos.add(hotArticleVo);
                    }
                }
            }

            //重新排序并缓存到redis
            hotArticleVos = hotArticleVos.stream().sorted(Comparator.comparing(HotArticleVo::getScore).reversed())
                    .collect(Collectors.toList());
            cacheService.set(key,JSON.toJSONString(hotArticleVos));
            if(key.equals(ArticleConstas.HOT_ARTICLE_FIRST_PAGE + apArticle.getChannelId())) {
                log.info("成功刷新{}频道中热点文章缓存数据",apArticle.getChannelName());
            } else {
                log.info("成功刷新推荐频道中热点文章缓存数据");
            }
        }
    }
}
